use std::{
    env::args,
    fs::File,
    io::{self, Read},
    time::{Duration, Instant, SystemTime, UNIX_EPOCH},
};

use chip8_core::chip8;
use crossterm::event::{self, KeyCode, KeyEventKind};
use ratatui::{
    DefaultTerminal,
    layout::Rect,
    style::{Style, Stylize},
    widgets::Block,
};

const FRAME_RATE: u64 = 60;
const KEYPAD_RESET_COUNTDOWN_INIT: u64 = 10;
const TICK_PER_FRAME: u8 = 8;
// chip8 supported keycode
const SUPPORTED_KEYCODE: [&str; 17] = [
    "1", "2", "3", "4", "q", "w", "e", "r", "a", "s", "d", "f", "z", "x", "c", "v", "ESC",
];

fn main() -> io::Result<()> {
    let mut terminal = ratatui::init();
    terminal.clear()?;
    let app_result = run(terminal);
    ratatui::restore();
    app_result
}

fn run(mut terminal: DefaultTerminal) -> io::Result<()> {
    // Init Chip8
    let mut chip8 = chip8::Chip8::default();

    // Try set random seed
    if let Ok(unix_timestamp) = SystemTime::now().duration_since(UNIX_EPOCH) {
        chip8.rng.seed = unix_timestamp.as_millis() as u16;
    }

    // Load FontSet
    chip8.load_fontset();

    // Load rom
    {
        let filename = args()
            .nth(1)
            .expect("Usage: ./<this_program> <your_rom.ch8>");
        let mut f = File::open(filename)?;

        let mut buffer = Vec::new();

        f.read_to_end(&mut buffer)?;

        chip8.load_rom(&buffer);
    }

    let tick_rate = Duration::from_millis(1000 / FRAME_RATE);

    let mut last_tick = Instant::now();

    let mut keypad_reset_countdown = KEYPAD_RESET_COUNTDOWN_INIT;

    loop {
        // Tick, set 8 times
        for _ in 0..TICK_PER_FRAME {
            if chip8.wait_for_key_release.0 {
                break;
            }
            chip8.tick();
        }

        // Tick timer
        chip8.tick_timer();
        // Beep
        if chip8.sound_timer > 0 {
            print!("\x07");
        }

        // Draw screen
        if chip8.draw_flag {
            chip8.draw_flag = false;
            terminal.draw(|frame| {
                chip8.screen.iter().enumerate().for_each(|(index, pixel)| {
                    let x = (index % chip8::SCREEN_WIDTH) as u16;
                    let y = (index / chip8::SCREEN_WIDTH) as u16;
                    if *pixel {
                        let area = Rect::new(x * 2, y, 2, 1);
                        let block = Block::default().style(Style::new().on_white());
                        frame.render_widget(block, area);
                    }
                });
            })?;
        }

        // Update keypad
        let timeout = tick_rate.saturating_sub(last_tick.elapsed());
        if event::poll(timeout)? {
            keypad_reset_countdown = KEYPAD_RESET_COUNTDOWN_INIT;

            if let event::Event::Key(key_event) = event::read()? {
                if key_event.code == KeyCode::Esc && key_event.kind == KeyEventKind::Press {
                    return Ok(());
                }

                // TODO: I want to do no_std println to debug what key code I type
                let (chip8_key_code, msg) =
                    pc_key_code_to_chip8_key_code(&mut terminal, &key_event.code).unwrap();

                {
                    match key_event.kind {
                        KeyEventKind::Press => {
                            chip8.keypad[chip8_key_code] = true;
                            match msg {
                                val if val == "ok".to_owned() => {}
                                _ => {
                                    println!(
                                        "\r\nINFO: bro, what the fuck are you typing, not supported keycode: {}\r\n
                                         \r\nINFO: the list of supported keycode are: {:?}\r\n",
                                         key_event.code,
                                        SUPPORTED_KEYCODE
                                    );
                                }
                            }
                        }
                        KeyEventKind::Release => {
                            chip8.keypad[chip8_key_code] = false;
                            if chip8_key_code == chip8.wait_for_key_release.1 {
                                chip8.wait_for_key_release.0 = false;
                            }
                        }
                        // Else events, ignore
                        _ => {}
                    }
                }
            }
        } else if !cfg!(windows) {
            // keypad reset count - 1
            keypad_reset_countdown -= 1;
            if keypad_reset_countdown == 0 {
                keypad_reset_countdown = KEYPAD_RESET_COUNTDOWN_INIT;

                chip8.keypad.iter_mut().for_each(|pressed| {
                    *pressed = false;
                });

                if chip8.wait_for_key_release.0 {
                    chip8.wait_for_key_release.0 = false;
                }
            }
        }

        // Update last tick
        last_tick = Instant::now();
    }
}

// I was about to let terminal clear when after println INFO, but it seems not works as I think
fn pc_key_code_to_chip8_key_code(
    _terminal: &mut DefaultTerminal,
    key_code: &KeyCode,
) -> Option<(usize, String)> {
    match key_code {
        KeyCode::Char('1') => Some((0x1, "ok".to_string())),
        KeyCode::Char('2') => Some((0x2, "ok".to_string())),
        KeyCode::Char('3') => Some((0x3, "ok".to_string())),

        // DO this modify just for tetris game
        // KeyCode::Char('q') | KeyCode::Tab => Some((0x4, "ok".to_string())),
        // KeyCode::Char('w') | KeyCode::Left => Some((0x5, "ok".to_string())),
        // KeyCode::Char('e') | KeyCode::Right => Some((0x6, "ok".to_string())),
        // KeyCode::Char('a') | KeyCode::Down => Some((0x7, "ok".to_string())),
        // end
        KeyCode::Char('4') => Some((0xC, "ok".to_string())),
        KeyCode::Char('q') => Some((0x4, "ok".to_string())),
        KeyCode::Char('w') => Some((0x5, "ok".to_string())),
        KeyCode::Char('e') => Some((0x6, "ok".to_string())),
        KeyCode::Char('r') => Some((0xD, "ok".to_string())),
        KeyCode::Char('a') => Some((0x7, "ok".to_string())),
        KeyCode::Char('s') => Some((0x8, "ok".to_string())),
        KeyCode::Char('d') => Some((0x9, "ok".to_string())),
        KeyCode::Char('f') => Some((0xE, "ok".to_string())),
        KeyCode::Char('z') => Some((0xA, "ok".to_string())),
        KeyCode::Char('x') => Some((0x0, "ok".to_string())),
        KeyCode::Char('c') => Some((0xB, "ok".to_string())),
        KeyCode::Char('v') => Some((0xF, "ok".to_string())),

        // Else keyboard ???, ignore
        _ => {
            Some(
                // (
                // "\r\nINFO: bro, what the fuck are you typing, not supported keycode: {}\r\n
                // \r\nINFO: the list of supported keycode are: {:?}\r\n",
                // key_code.to_string(),
                // SUPPORTED_KEYCODE
                (0, "err".to_string()),
            )
        }
    }
}
